<?php

namespace pmvc\ioc\xml;

use pmvc\util\XMLUtil;
use pmvc\ioc\PropertyDescriptor;
use pmvc\ioc\ObjectDescriptor;

use Exception;
use DOMElement;

/**
 * Allows for the processing of object descriptors.
 *
 */
class ObjectExtension
	implements Extension {

	/**
	 * {@inheritDoc}
	 */
	public function process(DOMElement $objectEl, ParseContext $pc) {
		$desc = $this->assimilateObject($objectEl, $pc);
		$pc->getObjectFactory()->addObjectDescriptor($desc);
	}

	/**
	 * Assimilates an object tag.
	 * 
	 * @param DOMElement $objectEl
	 * @param ParseContext $pc
	 * @return ObjectDescriptor
	 */
	private function assimilateObject(DOMElement $objectEl, ParseContext $pc) {

		// get constructor args
		$constructorArgs = Array();
		foreach ($pc->getXPath()->query("constructor-arg", $objectEl) as $ctrEl) {
			array_push($constructorArgs, $this->assimilateProperty($ctrEl, $pc));
		}

		// get properties
		$properties = Array();
		foreach ($pc->getXPath()->query("property", $objectEl) as $propertyEl) {
			array_push($properties, $this->assimilateProperty($propertyEl, $pc));
		}

		// get class data
		$class			= XMLUtil::getAttributeValue($objectEl, "class");
		$factoryClass	= XMLUtil::getAttributeValue($objectEl, "factoryClass");
		$uniqid			= uniqid('', TRUE)."#".$class;

		// get the rest of the data
		$id				= XMLUtil::getAttributeValue($objectEl, "id", $uniqid);
		$name			= XMLUtil::getAttributeValue($objectEl, "name");
		$singleton		= XMLUtil::getAttributeValue($objectEl, "singleton", "true");
		$abstract		= XMLUtil::getAttributeValue($objectEl, "abstract", "false");
		$parent			= XMLUtil::getAttributeValue($objectEl, "parent");
		$lazy			= XMLUtil::getAttributeValue($objectEl, "lazy-init", "true");
		$initMethod		= XMLUtil::getAttributeValue($objectEl, "init-method");
		$destroyMethod	= XMLUtil::getAttributeValue($objectEl, "destroy-method");
		$preloadFile	= XMLUtil::getAttributeValue($objectEl, "preloadFile");

		// create the descriptor
		$desc = new ObjectDescriptor(
			$id, $class, $name, $properties, $constructorArgs,
			strtolower($singleton)=="true", strtolower($abstract)=="true",
			$parent, strtolower($lazy)=="true", $initMethod, $destroyMethod,
			$factoryClass, $preloadFile);

		// return it
		return $desc;
	}

	/**
	 * Assimilates an array.
	 * 
	 * @param DOMElement $arrayEl
	 * @param ParseContext $pc
	 * @return Array:
	 */
	public function assimilateArray(DOMElement $arrayEl, ParseContext $pc) {
		$value = Array();
		foreach ($pc->getXPath()->query("entry", $arrayEl) as $entryEl) {
			array_push($value, $this->assimilateProperty($entryEl, $pc));
		}
		return $value;
	}

	/**
	 * Assimilates a property.
	 * 
	 * @param DOMElement $propertyEl
	 * @param ParseContext $pc
	 * @return \pmvc\ioc\PropertyDescriptor
	 */
	public function assimilateProperty(DOMElement $propertyEl, ParseContext $pc) {

		// get type name and value
		$type	= XMLUtil::getAttributeValueOrSubElementText($propertyEl, "type", PropertyDescriptor::TYPE_STRING);
		$name	= XMLUtil::getAttributeValueOrSubElementText($propertyEl, "name");
		$value	= XMLUtil::getAttributeValueOrSubElementText($propertyEl, "value");

		// if we don't have a value try the "ref" element
		if (!isset($value)) {
			$value = XMLUtil::getAttributeValueOrSubElementText($propertyEl, "ref");
			if ($value!=null && $value) {
				$type = PropertyDescriptor::TYPE_REF;
			}
		}

		// if it's not a ref lets assimilate arrays and objects
		if ($type!=PropertyDescriptor::TYPE_REF) {

			// is this an array?
			if (empty($value) && XMLUtil::hasChildElement($propertyEl, "array")) {
				$value = $this->assimilateArray($propertyEl->getElementsByTagName("array")->item(0), $pc);
				$type = PropertyDescriptor::TYPE_ARRAY;

				// is this an object?
			} else if (empty($value) && XMLUtil::hasChildElement($propertyEl, "object")) {
				$objDesc = $this->assimilateObject($propertyEl->getElementsByTagName("object")->item(0), $pc);
				$pc->getObjectFactory()->addObjectDescriptor($objDesc);
				$value = $objDesc->id;
				$type = PropertyDescriptor::TYPE_REF;
			}
		}

		// create the property descriptor
		return new PropertyDescriptor($name, $value, $type);
	}

}